const utils = {
  getValue(key, vm) {
    return vm.$data[key.trim()]
  },
  setValue(key, vm, newVal) {
    vm.$data[key] = newVal
  },
  model(node, value, vm) {
    const initVal = this.getValue(value, vm)
    // console.log(initVal)
    new Watcher(value, vm, (newValue) => {
      this.modelUpdater(node, newValue)
    })
    node.addEventListener('input', (e) => {
      const newVal = e.target.value
      this.setValue(value, vm, newVal)
    })
    this.modelUpdater(node, initVal)
  },
  text(node, value, vm) {
    let result
    if (value.includes('{{')) {
      // {{}}
      result = value.replace(/\{\{(.+)\}\}/g, (...args) => {
        console.log('args',args)
        const expr = args[1]
        new Watcher(expr, vm, (newVal) => {
          this.textUpdater(node, newVal)
        })
        return this.getValue(expr, vm)
      })
    } else {
      // v-text=**
      new Watcher(value, vm, (newVal) => {
        this.textUpdater(node, newVal)
      })
      result = this.getValue(value, vm)
    }
    this.textUpdater(node, result)
  },
  // utils[directorName](node, value, this.vm, eventName)
  on(node, value, vm, eventName) {
    node.addEventListener(eventName,vm.$methods[value].bind(vm))
  },
  textUpdater(node, value) {
    node.textContent = value
  },
  modelUpdater(node, value) {
    node.value = value
  }
}
class Watcher {
  constructor(expr, vm, cb) {
    this.expr = expr
    this.vm = vm
    this.cb = cb
    this.oldValue = this.getOldValue()
  }
  getOldValue() {
    Dep.target = this
    const oldValue = utils.getValue(this.expr, this.vm)
    Dep.target = null
    return oldValue
  }
  update() {
    const newValue = utils.getValue(this.expr, this.vm)
    // console.log('newValue=========== ', newValue)
    if (newValue !== this.oldValue) {
      this.cb(newValue)
    }
  }
}
class Dep {
  constructor() {
    this.collect = []
  }
  addWatcher(watcher) {
    this.collect.push(watcher)
  }
  notify() {
    this.collect.forEach(w => w.update())
  }
}
class Compiler {
  constructor(el, vm) {
    this.$el = this.isElementNode(el) ? el : document.querySelector(el)
    this.vm = vm
    const f = this.compileFragment(this.$el)
    this.compile(f)
    this.$el.appendChild(f)
  }
  compile(fragment) {
    const childNodes = Array.from(fragment.childNodes)
    childNodes.forEach(childNode => {
      if (this.isElementNode(childNode)) {
        // 标签节点 v-  v-on:click
        // console.dir(childNode)
        this.compileElement(childNode)
      } else if (this.isTextNode(childNode)) {
        // 文本节点 {{ }}
        // console.log('文本节点---',childNode)
        // console.dir(childNode)
        this.compileText(childNode)
      }
      if (childNode.childNodes && childNode.childNodes.length) {
        this.compile(childNode)
      }
    })
  }

  compileElement(node) {
    const attributes = Array.from(node.attributes)
    attributes.forEach(attr => {
      const { name, value } = attr
      if (this.isDirector(name)) {
        // 指令 v-model v-on:click v-text v-bind
        console.log(name)
        const [, director] = name.split('-')
        const [directorName, eventName] = director.split(':')
        utils[directorName](node, value, this.vm, eventName)
      } else if (this.isEventor(name)) {
        const eventName = name.split('@')[1]
        utils['on'](node, value, this.vm, eventName)
      }
    })
  }

  compileText(node) {
    const content = node.textContent
    if (/\{\{(.+)\}\}/.test(content)) {
      console.log(content)
      utils['text'](node, content, this.vm)
    }
  }

  compileFragment(el) {
    // console.dir(el)
    let f = document.createDocumentFragment()
    let firstChild
    while (firstChild = el.firstChild) {
      f.appendChild(firstChild)
    }
    // console.dir(f)
    return f
  }
  isEventor(name){
    return name.startsWith('@')
  }
  isDirector(name) {
    return name.startsWith('v-')
  }
  isTextNode(el) {
    return el.nodeType === 3
  }
  isElementNode(el) {
    return el.nodeType === 1
  }
}

class Observer {
  constructor(data) {
    this.observer(data)
  }
  observer(data) {
    if (data && typeof data === 'object') {
      Object.keys(data).forEach(key => {
        this.defineReactive(data, key, data[key])
      })
    }
  }
  defineReactive(data, key, value) {
    let that = this
    this.observer(value)
    let dep = new Dep()
    Object.defineProperty(data, key, {
      get() {
        console.log('get ', key, value)
        const target = Dep.target
        target && dep.addWatcher(target)
        return value
      },
      set(newVal) {
        if (newVal === value) return
        // console.log('set value ', value)
        console.log('set ', key, newVal)
        that.observer(newVal)
        value = newVal
        dep.notify()
      }
    })
  }
}


class Vue {
  constructor(options) {
    this.$el = options.el
    this.$data = options.data
    this.$methods = options.methods
    new Observer(this.$data)
    new Compiler(this.$el, this)
    this.proxyData(this.$data)
  }
  proxyData(data) {
    Object.keys(data).forEach(key => {
      Object.defineProperty(this, key, {
        get() {
          return data[key]
        },
        set(newVal) {
          data[key] = newVal
        }
      })
    })
  }
}